/* A Parser for APython */

%{
open Symbols;;

%}

%token <string> IDENT
%token NEWLINE INDENT DEDENT 
%token TRUE FALSE NONE
%token DEF CLASS APODEF RETURN PASS
%token <int> INTEGER 
%token <string> STRING APOSTRING
%token ASSIGN
%token COLON LPAREN RPAREN COMMA EOF DCOLON SEMI ARROW DOT
%token STAR DSTAR DASH PLUS SLASH PERCENT
%token IF ELSE WHILE FOR
%token LSHIFT RSHIFT AMP HAT BAR
%token LT LEQ EQ NEQ GEQ GT IN
%token LBRACE RBRACE
%token LAMBDA

%start apy_module
%type <Symbols.stmt list> apy_module
%%

/* expressions are basically things that have type */

atom:
    IDENT                                               { Ident $1 }
  | literal                                             { $1 }
  | enclosure                                           { $1 }
;

enclosure:
    LPAREN RPAREN                                       { Tuple [] }
  | LPAREN expression_list RPAREN                       { match $2 with (xs, false) -> List.hd xs | (xs, true) -> Tuple xs }
  | dict_display                                        { $1 }
;

literal:
    INTEGER                                             { Integer $1 }
  | STRING                                              { String $1 }
  | TRUE                                                { Boolean true }
  | FALSE                                               { Boolean false }
  | NONE                                                { Noneval }
;

primary:
    atom                                                { $1 }
  | primary DOT IDENT                                   { Attr_ref ($1, $3) }
  | primary LPAREN RPAREN                               { Call ($1, []) }
  | primary LPAREN expression_list RPAREN               { Call ($1, fst $3) }
;

lambda:
    LAMBDA LPAREN RPAREN COLON expression               { Lambda_expr ([], $5) }
  | LAMBDA LPAREN parameter_list RPAREN expression      { Lambda_expr ($3, $5) }
;

expression_list:
    expression                                          { ([$1],false) }
  | expression COMMA                                    { ([$1], true) }
  | expression COMMA expression_list                    { ($1:: (fst $3), true)  }
;

/*dictionaries*/
dict_display:
    LBRACE RBRACE                                       { Dict_display [] }
  | LBRACE key_datum_list RBRACE                        { Dict_display $2 }
  | LBRACE key_datum_list COMMA RBRACE                  { Dict_display $2 }
;

key_datum_list:
    key_datum                                           { [$1] }
  | key_datum COMMA key_datum_list                      { $1 :: $3 }
;

key_datum:
    expression COLON expression                         { ($1, $3) }
;

/* operators (sorry about the complexity) */
power:
    primary                                             { $1 }
  | primary DSTAR u_expr                                { Binop ("pow", $1, $3) }
;

/* unary expressions */
u_expr:
    power                                               { $1 }
  | DASH u_expr                                         { Monop ("neg", $2) }
  | PLUS u_expr                                         { $2 }
;

/* binary expressions */
m_expr:
    u_expr                                              { $1 }
  | m_expr STAR u_expr                                  { Binop ("mul", $1, $3) }
  | m_expr SLASH u_expr                                 { Binop ("div", $1, $3) }
  | m_expr PERCENT u_expr                               { Binop ("mod", $1, $3) }
;

a_expr:
    m_expr                                              { $1 }
  | a_expr PLUS m_expr                                  { Binop ("add", $1, $3) }
  | a_expr DASH m_expr                                  { Binop ("sub", $1, $3) }
;

shift_expr:
    a_expr                                              { $1 }
  | shift_expr LSHIFT a_expr                            { Binop ("lshift", $1, $3) }
  | shift_expr RSHIFT a_expr                            { Binop ("rshift", $1, $3) }
;

and_expr:
    shift_expr                                          { $1 }
  | and_expr AMP shift_expr                             { Binop ("and", $1, $3) }
;

xor_expr:
    and_expr                                            { $1 }
  | xor_expr HAT and_expr                               { Binop ("xor", $1, $3) }
;

or_expr:
    xor_expr                                            { $1 }
  | or_expr BAR xor_expr                                { Binop ("or", $1, $3) }
;

cmp_expr:
    or_expr                                             { $1 }
  | or_expr cmp_operator or_expr                        { Binop ($2, $1, $3) }
;

cmp_operator:
    LT                                                  { "lt" }
  | GT                                                  { "gt" }
  | EQ                                                  { "eq" }
  | IN                                                  { "contains" }
;

expression:
    cmp_expr                                            { $1 }
  | lambda                                              { $1 }
;

assignable:
    IDENT                                               { Ident $1 }
  | primary DOT IDENT                                   { Attr_ref ($1, $3) }
;

/* Simple statements that don't run over multiple lines */
simple_stmt:
    assignable ASSIGN expression                        { Assign ($1, $3) }
  | APODEF IDENT APOSTRING                              { Apodef ($2, $3) }
  | expression_list                                     { Expr_stmt (match $1 with (xs, false) -> List.hd xs | (xs, true) -> Tuple xs) }
  | RETURN expression_list                              { Return (match $2 with (xs, false) -> List.hd xs | (xs, true) -> Tuple xs) }
  | PASS                                                { Pass }
;

/* a load of (well, possibly only one) simple statements on a line */
stmt_list:
    simple_stmt                                     { [$1] }
  | simple_stmt SEMI                                { [$1] }
  | simple_stmt SEMI stmt_list                      { $1 :: $3 }
;

/* the actual statements */
statement:
    stmt_list NEWLINE                                   { Stmt_list $1 }
  | stmt_list EOF                                       { Stmt_list $1 }
  | compound_stmt                                       { $1 }
;

/* one or more statements */
statement_plus:
    statement                  { [$1] }
  | statement statement_plus   { $1 :: $2 }
;

/* the "body" of multiline statements */
suite:
    stmt_list NEWLINE                    { $1 }
  | NEWLINE INDENT statement_plus DEDENT { $3 }
;

/* compound statements */
compound_stmt:
    funcdef                                                 { $1 }
  | classdef                                                { $1 }
  | IF expression COLON suite ELSE COLON suite              { If_stmt ($2, $4, $7)}
  | IF expression COLON suite                               { If_stmt ($2, $4, [])}
  | WHILE expression COLON suite                            { While_stmt ($2, $4) }
  | FOR IDENT IN expression COLON suite                     { For_stmt (Ident $2, $4, $6) }
;

funcdef:
    DEF IDENT LPAREN RPAREN COLON suite                     { Funcdef ($2, [], $6) }
  | DEF IDENT LPAREN parameter_list RPAREN COLON suite      { Funcdef ($2, $4, $7) }
;

parameter_list:
    IDENT                                                   { [$1] }
  | IDENT COMMA parameter_list                              { $1 :: $3 }
;

classdef:
    CLASS IDENT COLON suite                                 { Classdef ($2, Ident "object", $4) }
  | CLASS IDENT LPAREN RPAREN COLON suite                   { Classdef ($2, Noneval, $6) }
  | CLASS IDENT LPAREN expression RPAREN COLON suite        { Classdef ($2, $4, $7) }
;

/* modules */
apy_module:
    EOF                        { [] }
  | statement apy_module       { $1 :: $2 }
  | NEWLINE apy_module         { $2 }
;
